package at.ltasat.train.classifier;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import at.ltasat.ArgumentClazzData;
import at.ltasat.ArgumentClazzDataClazzManagerIO;
import at.ltasat.CnfClassifierFactories;
import at.ltasat.CnfFeaturesFactory;
import at.ltasat.classifier.ClassifierFactory;
import at.ltasat.classifier.Clazz;
import at.ltasat.classifier.ClazzManager;
import at.ltasat.classifier.Membership;
import at.ltasat.classifier.ObjectFeatures;
import at.ltasat.classifier.Trainer;
import at.ltasat.classifier.io.ClassifierIO;
import at.ltasat.classifier.io.ClazzManagerIO;
import at.ltasat.train.set.TrainingSet;
import at.tasat.cnf.Cnf;
import at.tasat.io.OutputFormatter;

public final class TrainClassifierMain {

	public static void main(String[] args) {
		TrainClassifierConfiguration config = TrainClassifierConfiguration.get(args);
		int result = main(config);
		System.exit(result);
	}

	@SuppressWarnings("unchecked")
	public static int main(TrainClassifierConfiguration config) {
		final boolean verbose = config.isVerboseStatus();
		final PrintStream out = new PrintStream(config.getOut());
		try {
			if (config.isVerboseConfig()) {
				out.println(OutputFormatter.getCommentString(config.toString()));
			}
			log(verbose, out, "training started");
			final ClassifierFactory<Cnf, ArgumentClazzData> factory = getClassifierFactory(config.getClassifier());
			log(verbose, out, "reading training set");
			final ClazzManagerIO<ArgumentClazzData> clazzesIO = new ArgumentClazzDataClazzManagerIO();
			final ClazzManager<ArgumentClazzData> clazzes = clazzesIO.read(config.getIn());
			final TrainingSet<ArgumentClazzData> data = TrainingSet.read(config.getIn(), clazzes);
			config.getIn().close();
			log(verbose, out, "creating trainer");
			final Trainer<Cnf, ArgumentClazzData> trainer = factory.createTrainer(clazzes, CnfFeaturesFactory.INSTANCE);
			for (ObjectFeatures point : data.getPoints()) {
				log(verbose, out, "train on %s", point.toString());
				final int[] counts = data.getClassifications(point);
				final int sum = getSum(counts);
				final Membership<ArgumentClazzData>[] memberships = (Membership<ArgumentClazzData>[]) new Membership<?>[clazzes.size()];
				for (Clazz<ArgumentClazzData> clazz : clazzes) {
					memberships[clazz.getId()] = new Membership<ArgumentClazzData>(clazz, ((double) counts[clazz.getId()]) / sum);
				}
				trainer.train(point, memberships);
			}
			final OutputStream result = new FileOutputStream(config.getFile(), false);
			log(verbose, out, "creating and writing classfier to %s", config.getFile().getAbsolutePath());
			ClassifierIO.write(result, trainer.create(), clazzesIO);
			log(verbose, out, "training finished");
			return 0;
		} catch (Throwable error) {
			(new PrintStream(config.getErr())).println(OutputFormatter.getErrorString(error));
			return 1;
		}
	}

	private static void log(final boolean verbose, final PrintStream out, final String msg, final Object... args) {
		if (verbose) {
			out.println(OutputFormatter.getCommentString(String.format(msg, args)));
		}
	}

	private static ClassifierFactory<Cnf, ArgumentClazzData> getClassifierFactory(String name) {
		return CnfClassifierFactories.INSTANCE.get(name);
	}

	private static int getSum(final int[] array) {
		int sum = 0;
		for (int i : array) {
			sum += i;
		}
		return sum;
	}

}
